package uy.edu.fing.pgsegesb.authentication;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.jboss.remoting.InvocationRequest;
import org.jboss.remoting.transport.http.HTTPMetadataConstants;
import org.jboss.soa.esb.actions.ActionUtils;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.listeners.message.AbstractMessageComposer;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.message.Body;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.MessagePayloadProxy;
import org.jboss.soa.esb.message.Properties;
import org.jboss.soa.esb.message.ResponseHeader;
import org.jboss.soa.esb.message.MessagePayloadProxy.NullPayloadHandling;
import org.jboss.soa.esb.message.body.content.BytesBody;
import org.jboss.soa.esb.services.security.SecurityServiceException;
import org.jboss.soa.esb.services.security.auth.AuthenticationRequest;
import org.jboss.soa.esb.services.security.auth.ExtractionException;
import org.jboss.soa.esb.services.security.auth.ExtractorUtil;
import org.jboss.soa.esb.services.security.auth.SecurityInfoExtractor;
import org.jboss.soa.esb.services.security.auth.http.JbrHttpSecurityInfoExtractor;
import org.jboss.soa.esb.services.security.auth.ws.BinarySecurityTokenExtractor;
import org.jboss.soa.esb.services.security.auth.ws.SamlAssertionExtractor;
import org.jboss.soa.esb.services.security.auth.ws.UsernameTokenExtractor;

public class JBRMessageComposerSAML<T extends InvocationRequest> extends AbstractMessageComposer<T> 
{

    private MessagePayloadProxy payloadProxy;
    
    private JbrHttpSecurityInfoExtractor jbrHttpSecurityExtractor = new JbrHttpSecurityInfoExtractor();
    private final Set<SecurityInfoExtractor<String>> extractors = new LinkedHashSet<SecurityInfoExtractor<String>>();

    @Override
    public void setConfiguration(ConfigTree config) {
        super.setConfiguration(config);
        payloadProxy = new MessagePayloadProxy(config,
                new String[] {ActionUtils.POST_ACTION_DATA, Body.DEFAULT_LOCATION, BytesBody.BYTES_LOCATION},
                new String[] {ActionUtils.POST_ACTION_DATA});
        // Allow null to be set on as the message payload...
        payloadProxy.setNullSetPayloadHandling(NullPayloadHandling.LOG);
        
        final String securityNS = config.getAttribute("securityNS", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        extractors.add(new UsernameTokenExtractor(securityNS));
        extractors.add(new BinarySecurityTokenExtractor(securityNS));
        extractors.add(new SamlAssertionExtractor());
        System.out.println("Using the following security extractors: " + extractors);
    }

    @Override
    protected MessagePayloadProxy getPayloadProxy() {
        return payloadProxy;
    }

    @Override
    @SuppressWarnings("unchecked")
    protected void populateMessage(Message message, T invocationRequest) throws MessageDeliverException {

        // Set the payload from the JBR invocation...
    	Object obj = invocationRequest.getParameter();
        payloadProxy.setPayload(message, obj);

        // Copy the request properties onto the message...
        Map properties = invocationRequest.getRequestPayload();
        
        AuthenticationRequest authRequest = null;
        if (properties != null) {
            //  Extract any jbr http security information that may exist
            authRequest = jbrHttpSecurityExtractor.extractSecurityInfo(properties);
        }
        // Extract any ws security information that may exist
        Object payload = invocationRequest.getParameter();
        if ( authRequest == null && payload instanceof String) {
            
            try
            {
                authRequest = ExtractorUtil.extract((String)payload, extractors);
            }
            catch (ExtractionException e)
            {
                throw new MessageDeliverException(e.getMessage(), e);
            }
        }
            
        try {
            ExtractorUtil.addAuthRequestToMessage(authRequest, message);
        } catch (final SecurityServiceException e) {
            throw new MessageDeliverException(e.getMessage(), e);
        }


        // Purposely not iterating over the Map.Entry Set because there's
        // a bug in the Map impl used by JBossRemoting.  Not all the
        // "values" are actually in the Map.Entry set.  Some of them are handled
        // from within an overridden impl of the Map.get(Object) method.
        if (properties!=null)
        {
	        Set names = properties.keySet();
	        for (Object name : names) {
	            Object value = properties.get(name);
	            if(value != null) {
	                message.getProperties().setProperty(name.toString(), value);
	            }
	        }
        }
       
    }

    @Override
    public Object decompose(Message message, T invocationRequest) throws MessageDeliverException {
        Properties properties = message.getProperties();
        String propertyNames[] = properties.getNames();
        Map responseMap = getJBRResponseMap(invocationRequest);

        for(String name : propertyNames) {
            Object value = properties.getProperty(name);

            if(value instanceof ResponseHeader) {
                // JBESB-2511
                ResponseHeader header = (ResponseHeader)value;
                header.putNameValue(responseMap);
            }
            // JBESB-2761
            if(HTTPMetadataConstants.RESPONSE_CODE.equals(name) ||
                    HTTPMetadataConstants.RESPONSE_CODE_MESSAGE.equals(name)) {
                responseMap.put(name, value);
            }
        }

        return super.decompose(message, invocationRequest);
    }
    
    private static Map getJBRResponseMap(InvocationRequest invocationRequest) {
        Map responseMap = invocationRequest.getReturnPayload();

        if(responseMap == null) {
            responseMap = new LinkedHashMap();
            invocationRequest.setReturnPayload(responseMap);
        }
        return responseMap;
    }

}
